# Copyright (C) 2010 Iain Surgey.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
#
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

import sys
import os.path
import os
import re

import rx

from typedefs import *
from inline import Inline

class Convert:
	
	def __init__(self,infile,outfile):
		
		try:
			self.of = open(outfile,"w")
		except:
			print "File Error : Cannot open file for write. [%s]" % self.cf.name
			sys.exit()
		
		print "Writing to file : %s" % outfile
		
		self.infiles = [File(infile)]
		self.cf = self.infiles[0]
		
		self.macros = []
		self.inline = Inline(self)
		
		self.bitsh = {"2":"<<1","4":"<<2","8":"<<3","16":"<<4","32":"<<5","64":"<<6","128":"<<7","256":"<<8",\
						"512":"<<9","1024":"<<10","2048":"<<11","4096":"<<12","8192":"<<13","16384":"<<14",\
						"32768":"<<15","65536":"<<16"}
		
		self.loadfiles()
		self.loadhead()
		self.loadtypes()
		self.loadvars()
		self.loadbody()
		self.finalise()
		
	
	def finalise(self):
		
		self.of.write("\n}\n")
		self.of.close()
		
		for file in self.infiles:
			file.file.close()
		
		print "\nCompiling with HaXe...................................................\n"
		
		p = os.popen("haxe c.hxml")
		
		lines = p.readlines()
		for l in lines:
			print l
		#lines = p.stderr.readlines()
		#for l in lines:
		#	print l
		
	def loadfiles(self):
		line = ""
		while True:
			line = self.cf.read()
			if len(line) < 1:
				self.SyntaxError("Unexpected EOF")
			
			line =  line.strip()
			
			if len(line) > 0:
				if line.strip() == "#__end__":
					break
				self.infiles.append(File(line))
	
	def loadhead(self):
		self.types = {"int":Type("int",4,"Int"), "short":Type("short",2,"Int"), "float":Type("float",4,"Float"),  \
						"double":Type("double",8,"Float"),"long":Type("long",4,"Int"),"byte":Type("byte",1,"Int") \
						,"ushort":Type("ushort",2,"Int")}
		self.vars = {}
		self.vlist = []
		self.defs = ["private var __m__ : ByteArray;"]
		self.init = ["__m__ = new ByteArray();"]
		self.memofs = 4
		self.curline = 0
		
		self.acc = ""
		for file in self.infiles:
			self.cf = file
			self.curline = self.cf.line
			while True:
				line = file.read()
				if len(line) < 1:
					self.SyntaxError("Unexpected EOF")
				if line.strip() == "#__body__":
					if len(self.acc.strip()) > 0:
						self.SyntaxError("0WTF is this? - "+self.acc)
					break
				self.parseline(line,True)
		
	def loadbody(self):
		self.acc = ""
		
		for file in self.infiles:
			self.cf = file
			self.curline = self.cf.line
			while True:
				line = file.read()
				if len(line) < 1:
					self.of.write(self.acc)
					self.acc = ""
					break
				self.parseline(line,False)
	
	def loadtypes(self):
		count = 0
		lastcount = 0
		for k,t in self.types.items():
			if t.basic:
				continue
			self.cf = t.file
			while len(t.varacc.strip()) > 0:
				m = rx.vardef.match(t.varacc)
				if m:
					t.varacc = t.varacc[m.end():].strip()
					rl = self.cf.line
					self.cf.line = t.line
					self.addvar(m.group(1),m.group(2),m.group(3),m.group(4).strip(),t.vars,t.vlist)
					self.cf.line = rl
				else:
					self.SyntaxError2("1WTF is this? - "+t.varacc,t.line)
			
			t.complete = True
			
			for v in t.vlist:
				if not v.type.complete and v.ptr == 0:
					t.complete = False
					count = count+1
					break
			
			if t.complete:
				self.setofs(t)
		
		while True:
			lastcount = count
			count = 0
			for k,t in self.types.items():
				if t.basic or t.complete:
					continue
				
				t.complete = True
				
				for v in t.vlist:
					if not v.type.complete and v.ptr == 0:
						t.complete = False
						count = count + 1
						break
				
				if t.complete:
					self.setofs(t)
			
			if count == 0:
				break
			
			if count >= lastcount:
				print count
				print lastcount
				for k,t in self.types.items():
					if t.basic or t.complete:
						continue
					t.complete = True
					for v in t.vlist:
						if not v.type.complete and v.ptr == 0:
							self.SyntaxError2("Incomplete Type - "+v.type.name,t.line)
				break
	
	def loadvars(self):
		
		for v in self.vlist:
			vatemp = v.arr
			v.arr = []
			acount = len(vatemp)
			for i in range(0,acount):
				v.arr.append("")
			while acount > 0:
				if acount == len(vatemp):
					if v.ptr > 0:
						v.arr[acount-1] = "<<2"
						vatemp[acount-1] = 4*vatemp[acount-1]
					else:
						mult = ""
						if v.type.len == 1:
							mult = ""
						else:
							mult = str(v.type.len)
						if mult in self.bitsh.keys():
							v.arr[acount-1] = self.bitsh[mult]
						else:
							if len(mult) > 0:
								v.arr[acount-1] = " * "+mult
						vatemp[acount-1] = v.type.len*vatemp[acount-1]
				else:
					mult = str(vatemp[acount])
					if mult in self.bitsh.keys():
						v.arr[acount-1] = self.bitsh[mult]
					else:
						v.arr[acount-1] = " * "+mult
					vatemp[acount-1] = vatemp[acount]*vatemp[acount-1]
				acount = acount-1
			
			
			if not v.type.complete and v.ptr == 0:
				self.SyntaxError2("Incomplete Type - "+v.type.name,v.line)
			
			if v.ptr > 0:
				if len(v.arr) > 0:
					v.ofs = self.memofs
					self.memofs = self.memofs + (4*v.atotal)
				else:
					self.defs.append("private var "+v.name+" : Int;")
			else:
				if len(v.arr) > 0:
					v.ofs = self.memofs
					self.memofs = self.memofs + (v.type.len*v.atotal)
				else:
					if v.type.basic:
						self.defs.append("private var "+v.name+" : "+v.type.htype+";")
					else:
						v.ofs = self.memofs
						self.memofs = self.memofs + v.type.len
				
		
		if self.memofs < 1024:
			self.memofs = self.memofs+(1024-self.memofs);
		self.init.append("__m__.length = "+str(self.memofs)+";")
		self.init.append("Memory.select(__m__);")
	
	def setofs(self,t):
		for v in t.vlist:
			vatemp = v.arr
			v.arr = []
			acount = len(vatemp)
			for i in range(0,acount):
				v.arr.append("")
			while acount > 0:
				if acount == len(vatemp):
					if v.ptr > 0:
						v.arr[acount-1] = "<<2"
						vatemp[acount-1] = 4*vatemp[acount-1]
					else:
						mult = ""
						if v.type.len == 1:
							mult = ""
						else:
							mult = str(v.type.len)
						if mult in self.bitsh.keys():
							v.arr[acount-1] = self.bitsh[mult]
						else:
							if len(mult) > 0:
								v.arr[acount-1] = " * "+mult
						vatemp[acount-1] = v.type.len*vatemp[acount-1]
				else:
					mult = str(vatemp[acount])
					if mult in self.bitsh.keys():
						v.arr[acount-1] = self.bitsh[mult]
					else:
						v.arr[acount-1] = " * "+mult
					vatemp[acount-1] = vatemp[acount]*vatemp[acount-1]
				acount = acount-1
			
			v.ofs = t.len
			if v.ptr > 0:
				t.len = t.len + (4*v.atotal)
			else:
				t.len = t.len + (v.type.len*v.atotal)
	
	def parseline(self,line,head):
		#line = line.strip()
		excess = ""
		
		if self.cf.comment:
			loc = line.find("*/")
			if loc > -1:
				line = line[loc+2:]+"\n"
				self.cf.comment = False
				self.parseline(line,head)
			return
		
		if not self.cf.comment:
			wloc = line.find("//")
			if wloc > -1:
				line = line[:wloc]+"\n"
		
		loc = line.find("/*")
		if loc > -1:
			excess = line[loc+2:]
			line = line[:loc]
			self.cf.comment = True
		
		line = self.runmacros(line,0)
		
		if head:
			self.parsehead(line.strip())
		else:
			self.parsebody(line)
		
		if len(excess) > 0:
			self.parseline(excess,head)
	
	def parsebody(self,line):
		self.acc = self.acc + line
		
		if not self.cf.defs:
			loc = self.acc.find("#__def__")
			if loc > -1:
				self.cf.defs = True
				ex = self.acc[loc+8:]
				self.acc = self.acc[:loc]
				self.parsebody("")
				self.of.write(self.acc)
				self.acc = ""
				self.of.write(self.liout(self.defs))
				self.acc = ex
				
		
		if not self.cf.init:
			loc = self.acc.find("#__init__")
			if loc > -1:
				self.cf.defs = True
				ex = self.acc[loc+9:]
				self.acc = self.acc[:loc]
				self.parsebody("")
				self.of.write(self.acc)
				self.acc = ""
				self.of.write(self.liout(self.init))
				self.acc = ex
				
		
		m = rx.inline.search(self.acc)
		if m:
			self.of.write(self.acc[:m.start()])
			self.addinline(m.group(1))
			self.acc = self.acc[m.end():]
			self.parsebody("")
			
	
	def parsehead(self,line):
		self.acc = self.acc +" "+ line
		m = rx.define.match(self.acc)
		if m:
			self.curline = self.cf.line
			self.acc = self.acc[m.end():]
			self.addmacro(m.group(1),m.group(2))
		
		m = rx.struct.match(self.acc)
		if m:
			self.curline = self.cf.line
			self.acc = self.acc[m.end():]
			self.addtype(m.group(2),m.group(1))
		
		m = rx.vardef.match(self.acc)
		if m:
			self.curline = self.cf.line
			self.acc = self.acc[m.end():]
			self.addvar(m.group(1),m.group(2),m.group(3),m.group(4).strip(),self.vars,self.vlist)
	
	
	def liout(self,slist):
		out = ""
		for s in slist:
			out = out + s + "\n"
		return out
	
	def addinline(self,dat):
		if dat[:2] == "d|" or dat[:2] == "s|":
			sil = False
			if dat[:2] == "s|":
				sil = True
			dat = dat[2:]
			m = rx.ivardef.match(dat)
			if not m:
				self.SyntaxError("Invalid variable definition")
			self.curline = self.cf.line
			self.addvar(m.group(1),m.group(2),m.group(3),m.group(4).strip(),self.vars,self.vlist,True,sil)
		else:
			self.of.write(self.inline.parse(dat))
	
	def addtype(self,name,vars):
		if name in self.types.keys():
			self.SyntaxError("Duplicate type definition - "+name)
		t = Type(name)
		t.line = self.curline
		t.file = self.cf
		t.varacc = vars
		#print "Adding type : "+t.name
		self.types[name] = t
	
	def addvar(self,type,ptr,name,arr,vars,vlist,inl=False,sil=False):
		
		if type not in self.types.keys():
			self.SyntaxError("Unknown type - "+type)
		
		if name in vars.keys() and vars[name].inl == False:
			self.SyntaxError("Duplicate variable name - "+name)
		
		v = Var(name,self.types[type])
		v.line = self.curline
		v.file = self.cf
		
		arr = arr.strip()
		excess = None
		loc = arr.find(",")
		if loc > -1:
			excess = arr[loc+1:].strip()
			arr = arr[:loc].strip()
		
		#vatemp = []
		
		#Evaluate array lengths
		while arr != None and len(arr) > 0:
			m = rx.arrdef.match(arr)
			if m:
				arr = arr[m.end():].strip()
				v.arr.append(self.parsearr(m.group(1)))
				#v.arr.append("")
			else:
				self.SyntaxError("Invalid array definition - "+arr)
		
		v.atotal = 1
		for a in v.arr:
			v.atotal = v.atotal*a
		
		
		#Count pointer dereferences
		while ptr != None and len(ptr) > 0:
			if ptr[:1] == "*":
				v.ptr = v.ptr + 1
			ptr = ptr[1:]
		
		
		if inl:
			if len(v.arr) > 0 and not sil:
				self.SyntaxError("Inline definitions cannot be arrays")
			
			if v.ptr > 0:
				if not sil:
					self.of.write("var "+v.name+" : Int;\n")
			elif v.type.basic:
				if not sil:
					self.of.write("var "+v.name+" : "+v.type.htype+";\n")
			else:
				self.SyntaxError("Non-pointer inline definitions can only be basic types")
			
			v.inl = True
			
			
		
		#Add haxe Int for pointers (done in loadvars)
		#if v.ptr > 0 and len(v.arr) == 0 and vars == self.vars:
		#	self.defs.append("private var "+v.name+" : Int;")
		
		vars[name] = v
		vlist.append(v)
		#print "Adding Var : "+v.name
		
		
		
		if excess != None and len(excess) > 0:
			m = rx.varnext.match(excess)
			self.addvar(type,m.group(1),m.group(2),m.group(3).strip(),vars,vlist,inl,sil)
			
		
	def parsearr(self,dat):
		if dat[:1] != "[" or dat[-1:] != "]" or rx.alpha.search(dat):
			self.SyntaxError("Invalid Array definition - "+dat)
		
		dat = dat[1:-1]
		
		try:
			return eval(dat)
		except:
			self.SyntaxError("Error evaluating array - "+dat)
		
	def runmacros(self,line,start):
		if len(line) < 1:
			return ""
		for i in range(start,len(self.macros)):
			c = self.macros[i]
			m = c.re.search(line)
			if m:
				return self.runmacros(line[:m.start(1)],i+1) + c.value + self.runmacros(line[m.end(1):],i)
		return line
	
	def addmacro(self,name,value):
		for c in self.macros:
			if c.name == name:
				self.SyntaxError("Duplicate macro definition - "+name)
		
		self.macros.append(Macro(name,value))
				
	def SyntaxError(self,msg):
		print "Syntax Error : ["+self.cf.name+" | line "+str(self.cf.line)+"] "+msg
		sys.exit()
	
	def SyntaxError2(self,msg,line):
		print "Syntax Error : ["+self.cf.name+" | line "+str(line)+"] "+msg
		sys.exit()
